/* 
 *File: interp.c
 * ------------------------
 *  This file is implements the main program for the MFL interpretor.
 *
 * Authors: Olof Ivarsson och Johanna Carlsson
 */

#include <stdio.h>
#include "genlib.h"
#include "strlib.h"
#include "simpio.h"
#include "exception.h"
#include "exp.h"
#include "scanadt.h"
#include "parser.h"
#include "env.h"
#include "eval.h"
#include "symtab.h"

typedef void (*interpT)(scannerADT scanner, environmentADT env);

typedef struct{
	interpT fn;
}*cmdEntryT;

/* Function prototypes */
static void printValue(valueADT value);
static void initCMDTable(symtabADT cmdTable);
static void defineCommand(symtabADT cmdTable, string cmd, interpT func);
static void executeCommand(symtabADT cmdTable, scannerADT scanner, environmentADT env);

static void quit(scannerADT scanner, environmentADT env);
static void help(scannerADT scanner, environmentADT env);
static void define(scannerADT scanner, environmentADT env);
static void load(scannerADT scanner, environmentADT env);
static void type(scannerADT scanner, environmentADT env);

/* Main program */
main()
{
	scannerADT scanner;
	environmentADT env;
	expADT exp;
	string line,token;
	valueADT value;
	symtabADT cmdTable;

	cmdTable = NewSymbolTable();
	env = NewEnvironment();

	scanner = NewScanner();
	SetScannerSpaceOption(scanner, IgnoreSpaces);
	initCMDTable(cmdTable);

	printf("MFL interpreter (type \":help\" for help)\n");
	printf("    (c) Olof Ivarsson & Johanna Carlsson\n");



	while (TRUE) {
		try {
			printf("=> ");
			line = GetLine();
			SetScannerString(scanner, line);
			token = ReadToken(scanner);
			if(StringEqual(token,":")){
				executeCommand(cmdTable, scanner, env);
			} else{
				SaveToken(scanner,token);
				exp = parseExp(scanner);
				value = eval(exp,env);
				printValue(value);
			}
			except(ErrorException)
			printf("Error: %s\n", (string) GetExceptionValue());
		} endtry
	}

	FreeScanner(scanner);
	FreeSymbolTable(cmdTable);
}

/*
 * Function: printValue
 * Usage: printValue(value);
 * ------------------------------
 * This function print the data in value. 
 */
void printValue(valueADT value){
	if(ValueType(value) == IntValue)
		printf("%d\n",GetIntValue(value));
	else if (ValueType(value) == FuncValue)
		printf("<function with one argument>\n");
	else
		Error("Illegal valueType");
}


/*
 * Function: initCMDTable
 * Usage: initCMDTable(cmdTable);
 * ------------------------------
 * This function initializes the symboltable for commands.
 */
static void initCMDTable(symtabADT cmdTable){	
	defineCommand(cmdTable, "load", load);
	defineCommand(cmdTable, "l", load);

	defineCommand(cmdTable, "define",define);
	defineCommand(cmdTable, "d",define);

	defineCommand(cmdTable, "help", help);
	defineCommand(cmdTable, "h", help);

	defineCommand(cmdTable, "type", type);
	defineCommand(cmdTable, "t", type);

	defineCommand(cmdTable, "quit", quit);
	defineCommand(cmdTable, "q", quit);
}

/*
 * Function: defineCommand
 * Usage: defineCommand(cmdTable, cmd, func);
 * ------------------------------
 * This function handles the entering of commands into the cmdTable.
 */
static void defineCommand(symtabADT cmdTable, string cmd, interpT func){
	cmdEntryT entry;
	entry = New(cmdEntryT);

	entry->fn = func;
	Enter(cmdTable, cmd, entry);
}

/*
 * Function: executeCommand
 * Usage: executeCommand(cmdTable, scanner, env);
 * ------------------------------
 * This function execute a specific command.
 */
static void executeCommand(symtabADT cmdTable, scannerADT scanner, environmentADT env){
	cmdEntryT entry;
	string cmd;
	cmd = ReadToken(scanner);

	entry = (cmdEntryT)Lookup(cmdTable, cmd);
	if(entry == UNDEFINED){
		printf("Undefined command: %s\n",cmd);
		return;
	}
	entry->fn(scanner, env);	
}

/*
 * Function: define
 * Usage: define(scanner, env);
 * ------------------------------
 * This function checks if a defintion is legal if so, calls the function
 * which defines an identifier.
 */
void define(scannerADT scanner, environmentADT env){
	string token,id;
	expADT exp;

	id = ReadToken(scanner);
	if(StringEqual(ReadToken(scanner),"="))
		exp = readE(scanner);
	else
		Error("Expecting '=' in define\n");

	if(MoreTokensExist(scanner))
		Error("Define: %s unexpected", ReadToken(scanner));

	DefineIdentifier(env,id,exp,env);
}

/*
 * Function: help
 * Usage: help(scanner, env);
 * ------------------------------
 * This function prints the commands availible for the user to use.
 */
void help(scannerADT scanner, environmentADT env){
	printf("Available commands\n");
	printf("  :load <file>        Load a file with definitions.\n");
	printf("  :define <id>=<exp.> Define a identifier.\n");
	printf("  :type <expression>  Show the type of the given expression.\n");
	printf("  :help               Display this text.\n");
	printf("  :quit               Exit the interpreter.\n");
}

/*
 * Function: type
 * Usage: type(scanner, env);
 * ------------------------------
 * This function checks what type the user given expression is.
 */
void type(scannerADT scanner, environmentADT env){
	expADT exp;
	valueADT value;
	string arg;

	arg="";

	while(MoreTokensExist(scanner)){
		arg = Concat(arg,ReadToken(scanner));
	}

	SetScannerString(scanner,arg);

	exp = parseExp(scanner);
	value = eval(exp,env);

	if(ValueType(value) == IntValue)
		printf("%s :: Int\n",arg);
	else
		printf("%s :: Function with one argument\n",arg);
}

/*
 * Function: quit
 * Usage: quit(scanner, env);
 * ------------------------------
 * This function quit the program.
 */
void quit(scannerADT scanner, environmentADT env){
	exit(0);
}

/*
 * Function: load
 * Usage: load(scanner, env);
 * ------------------------------
 * This function load a file into the enviroments symboltable.
 */
void load(scannerADT scanner, environmentADT env){
	FILE *infile;
	string line, scannerString, fileName;
	scannerADT defineScanner;
	
	scannerString = "";
	fileName = "";

	while(MoreTokensExist(scanner))
		fileName = Concat(fileName,ReadToken(scanner));

	infile = fopen(fileName,"r");
	if(infile != NULL){
		defineScanner = NewScanner();
		SetScannerSpaceOption(defineScanner, IgnoreSpaces);

		line = ReadLine(infile);
		while(line != NULL){
			if(line[0] == ' '){
				scannerString = Concat(scannerString,line);
				line = ReadLine(infile);
			}
			else if(line[0] != '#'){

				scannerString = Concat(scannerString,line);

				line = ReadLine(infile);
				
				if(line[0] != ' ' && !StringEqual(scannerString,"")){
					//printf("DEFINE:\n %s\n\n",scannerString);
					SetScannerString(defineScanner,scannerString);
					define(defineScanner,env);
					scannerString = "";
				}
			}
			else
				line = ReadLine(infile);
		}
		fclose(infile);
		FreeScanner(defineScanner);
	}
	else
		Error("Can't open file: %s\n",fileName);
}
